Skip to content

feat(keymap)!: replace expr keymaps with normalized feedkeys execution #2266

Merged
saghen merged 12 commits into
mainfrom
fix/keymap-multikey
Mar 14, 2026
Merged

feat(keymap)!: replace expr keymaps with normalized feedkeys execution #2266
saghen merged 12 commits into
mainfrom
fix/keymap-multikey

Conversation

@soifou
Copy link
Copy Markdown
Collaborator

@soifou soifou commented Nov 21, 2025

Revamp keymap handling by moving away from expr mappings to a normalized feedkeys approach, fixing several long-standing edge cases around multi-key mappings, fallbacks, and interoperability with other plugins.

  • Handle multi-key sequences
{
    ['jk'] = { 'hide', 'fallback' },
    ['<C-x><C-o>'] = { 'show', 'fallback' },
    ['<Leader>cc'] = { 'show', 'fallback' },
}
  • Preserve key special notation when fallback
vim.keymap.set('i', '<C-i>', function() vim.print('fallback to <C-i>') end, { unique = false })
vim.keymap.set('i', '<Tab>', function() vim.print('fallback to <Tab>') end, { unique = false })
vim.keymap.set('i', '<C-m>', function() vim.print('fallback to <C-m>') end, { unique = false })

{       
    ['<C-i>'] = { 'accept', 'snippet_forward', 'fallback' }, -- when fall back prints "fallback to <C-i>"
    ['<C-m>'] = { 'accept', 'fallback' }, -- when fall back prints "fallback to <C-m>"
}
  • Key composition
{
    ['<C-n>'] = { 'select_next' },
    ['<Tab>'] = {
       function() return '' end, -- would fallback
       function() return '<C-n>' end, -- would call `select_next`
    },
},
  • Recursive keymap
vim.keymap.set('i', '<F2>', '<Esc>:echo "Hello from F2"<CR>a', { noremap = true, silent = true })
{
   ['<Space><Space>'] = { function() return '<F2>' end }, -- would print "Hello from F2"
}
  • Handle script mappings correctly: <SNR>, <SID>, <Plug>
vim.cmd([[
  function! s:MyFunction()
    echo "script local function invoked"
  endfunction

  nnoremap <silent> <SNR>42_mykey :call <SID>MyFunction()<CR>
  nnoremap <silent> <Plug>(MyPlug) :echo "Plug mapping triggered"<CR>
]])

{
    ['<Leader>m'] = { function() return '<Esc><Plug>(MyPlug)a' end }, -- prints "Plug mapping triggered"
}
  • Handle remap mappings correctly
  • Apply blink.cmp keymaps eagerly when triggering snippets from normal mode
  • Reapply blink.cmp keymaps if stolen by others
  • Refactoring:
    • Consolidate and keep the keymap module as simply as possible
    • Don't rely anymore on expr,
    • Drop vim.schedule in all commands and return boolean value from the function itself
  • Documentation: Revamp the keymap section

Closes #406
Closes #453
Closes #714
Closes #1879
Closes #2119
Closes #2164
Closes #2182
Closes #2263
Closes #2333
Closes #2384

@soifou

This comment was marked as outdated.

@saghen
Copy link
Copy Markdown
Owner

saghen commented Nov 21, 2025

Does calling feedkeys have any caveats that we should be aware of? If not, I'd like to adopt it for all the keymaps! Avoiding expr = true would avoid having to vim.schedule anything that edits the buffer, which would be great for scriptability. I don't mind the way you're detecting multi-key mappings but it would be great if there was a less hacky way (maybe replace_keycodes could help?)

@soifou
Copy link
Copy Markdown
Collaborator Author

soifou commented Nov 22, 2025

Thanks for the feedback, you give me some additional thinking! I'd like to explore this approach a bit more. The feedkeys solution does seem cleaner and we could get rid of multi-key detection if we turn off expr.

Edit: Updated PR description

@soifou soifou changed the title fix(keymap): handle multi-key mappings correctly refactor(keymap): improve fallback and key handling Nov 24, 2025
@soifou soifou marked this pull request as draft November 24, 2025 18:27
@saghen
Copy link
Copy Markdown
Owner

saghen commented Nov 25, 2025

Wow this is quite a bit of work, you'd end up fixing all the major issues with keymaps! These changes would be breaking, since theyre no longer vim.schedule so it might make sense to artificially vim.schedule or merge this into a new v2 branch.

Fyi, I got a bit ambitious with v2 so I'm going to reduce its scope. Just planning some light refactoring, these keymap changes, source-per-LSP, adopt blink.lib, and the improved API (for keymaps, enable, vim.g, dynamic config, etc)

@soifou
Copy link
Copy Markdown
Collaborator Author

soifou commented Nov 25, 2025

Yeah, as long as I'm here, I might as well fix these issues once and for all. I've had promising results locally. I don't mind working on this for v2, but last time I switched to this branch, everything was so broken that it was hard to make any convincing commits. Feel free to keep in touch on Matrix to coordinate!

@soifou soifou force-pushed the fix/keymap-multikey branch 4 times, most recently from fec6db0 to c855892 Compare December 22, 2025 13:48
@soifou soifou changed the title refactor(keymap): improve fallback and key handling feat(keymap)!: replace expr keymaps with normalized feedkeys execution Dec 22, 2025
@soifou soifou force-pushed the fix/keymap-multikey branch from c855892 to d080c78 Compare February 6, 2026 01:20
@soifou soifou force-pushed the fix/keymap-multikey branch from d080c78 to 530299a Compare February 7, 2026 12:13
@phanen

This comment was marked as resolved.

@soifou

This comment was marked as resolved.

@phanen

This comment was marked as resolved.

@soifou

This comment was marked as resolved.

@soifou soifou force-pushed the fix/keymap-multikey branch from c3a2351 to b784f83 Compare February 14, 2026 16:41
Copy link
Copy Markdown
Owner

@saghen saghen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is some brilliant work, thank you so much! Going to start daily driving this

Comment thread lua/blink/cmp/keymap/init.lua
Comment thread lua/blink/cmp/keymap/init.lua Outdated
@saghen saghen force-pushed the fix/keymap-multikey branch from 83c8837 to f4ec48f Compare March 10, 2026 20:27
@saghen saghen marked this pull request as ready for review March 10, 2026 20:27
@saghen
Copy link
Copy Markdown
Owner

saghen commented Mar 10, 2026

Lmk if you're happy with it and I'll merge it after the next release (release after 1.10 will be 2.0)

@saghen saghen force-pushed the fix/keymap-multikey branch from f4ec48f to e42586f Compare March 10, 2026 20:34
@soifou
Copy link
Copy Markdown
Collaborator Author

soifou commented Mar 11, 2026

Lmk if you're happy with it and I'll merge it after the next release (release after 1.10 will be 2.0)

Yes, this seems stable enough!

Note I was initially hesitant about 2bf5cf8 due to your comment, so I focused only on the keymap module. This commit closes #2333, #2381 (with minor updates), and #2384. But will this still break existing user configs? I'm still not sure.

Before merging, we need to:

  1. Update the keymap documentation to reflect these changes
  2. Link all closed issues
  3. Decide whether it will break configs

Overall, I'm super excited to see this on your radar and see it land!

soifou added 3 commits March 13, 2026 13:14
This change introduces:
- multi-key sequence support (e.g. `<C-x><C-o>`)
- normalized key comparison and equivalence (`<C-i>` == `<Tab>`)
- key composition from user callbacks
- fallback handling, including `<Plug>`, `<SID>`, and script mappings
- automatic reapplication of missing (stolen) buffer keymaps

Keymaps are now buffer-local for all modes, rely on normalized
key notation instead of termcodes, and execute commands eagerly
until one succeeds.

BREAKING CHANGE: Keymap execution semantics, fallback behavior, and
mapping scope have changed. expr mappings are no longer used.
@soifou soifou force-pushed the fix/keymap-multikey branch from dadb6a1 to 288a776 Compare March 13, 2026 12:31
@saghen
Copy link
Copy Markdown
Owner

saghen commented Mar 13, 2026

Decide whether it will break configs

Yeah I initially wanted to wait for v2, but I'm about to make the 1.10 release and next will be 2.0, so we can begin making breaking changes

@soifou
Copy link
Copy Markdown
Collaborator Author

soifou commented Mar 13, 2026

Excellent, I was worried things were getting too stable!

@saghen
Copy link
Copy Markdown
Owner

saghen commented Mar 13, 2026

Gotta give the people something to tinker with in their config 😂

@saghen
Copy link
Copy Markdown
Owner

saghen commented Mar 14, 2026

Merging without the documentation changes because I want to start the bigger refactors, but don't want to cause tons of merge conflicts for ya. Can't say it enough, brilliant work on this

@saghen saghen merged commit af18529 into main Mar 14, 2026
6 checks passed
@saghen saghen deleted the fix/keymap-multikey branch March 14, 2026 21:33
soifou added a commit that referenced this pull request Mar 31, 2026
Revamped the keymap documentation to align with recent API changes
introduced in #2266.
saghen added a commit that referenced this pull request Apr 1, 2026
Revamped the keymap documentation to align with recent API changes
introduced in #2266.

---------

Co-authored-by: Liam Dyer <liamcdyer@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment